//! Defines a domain-specific language (DSL) for describing x64 instructions.
//!
//! This language is intended to be:
//! - compact--i.e., define an x64 instruction on a single line, and
//! - a close-to-direct mapping of what we read in the x64 reference manual.

mod custom;
mod encoding;
mod features;
pub mod format;

pub use custom::{Custom, Customization};
pub use encoding::{Encoding, ModRmKind, OpcodeMod};
pub use encoding::{Evex, Length, Vex, VexEscape, VexPrefix, evex, vex};
pub use encoding::{
    Group1Prefix, Group2Prefix, Group3Prefix, Group4Prefix, Opcodes, Prefixes, Rex, TupleType, rex,
};
pub use features::{ALL_FEATURES, Feature, Features};
pub use format::{Eflags, Extension, Format, Location, Mutability, Operand, OperandKind, RegClass};
pub use format::{align, fmt, implicit, r, rw, sxl, sxq, sxw, w};

/// Abbreviated constructor for an x64 instruction.
pub fn inst(
    mnemonic: impl Into<String>,
    format: Format,
    encoding: impl Into<Encoding>,
    features: impl Into<Features>,
) -> Inst {
    let encoding = encoding.into();
    encoding.validate(&format.operands);
    Inst {
        mnemonic: mnemonic.into(),
        format,
        encoding,
        features: features.into(),
        alternate: None,
        has_trap: false,
        custom: Custom::default(),
    }
}

/// An x64 instruction.
///
/// Use [`inst`] to construct this within the
/// [`instructions`](super::instructions) module. This structure is designed to
/// represent all of the information for one instruction (a table row) in the
/// x64 _Instruction Set Reference_ or at least enough to generate code to emit
/// the instruction.
pub struct Inst {
    /// The instruction name as represented in the x64 reference manual. This is
    /// the pretty-printed name used for disassembly. Multiple instructions may
    /// have the same mnemonic, though; the combination of this field and the
    /// format name must be unique (see [`Inst::name`]).
    pub mnemonic: String,
    /// The instruction operands, typically represented in the "Instruction"
    /// column of the x64 reference manual.
    pub format: Format,
    /// The instruction encoding, typically represented in the "Opcode" column
    /// of the x64 reference manual.
    pub encoding: Encoding,
    /// The CPU features required to use this instruction; this combines the
    /// "64-bit/32-bit Mode Support" and "CPUID Feature Flag" columns of the x64
    /// reference manual.
    pub features: Features,
    /// An alternate version of this instruction, if it exists.
    pub alternate: Option<Alternate>,
    /// Whether or not this instruction can trap and thus needs a `TrapCode`
    /// payload in the instruction itself.
    pub has_trap: bool,
    /// Whether or not this instruction uses custom, external functions
    /// instead of Rust code generated by this crate.
    pub custom: Custom,
}

impl Inst {
    /// The unique name for this instruction.
    ///
    /// To avoid ambiguity, this name combines the instruction mnemonic and the
    /// format name in snake case. This is used in generated code to name the
    /// instruction `struct` and builder functions.
    ///
    /// In rare cases, this `<mnemonic>_<format>` scheme does not uniquely
    /// identify an instruction in x64 ISA (e.g., some extended versions,
    /// VEX/EVEX). In these cases, we append a minimal identifier to
    /// the format name (e.g., `sx*`) to keep this unique.
    #[must_use]
    pub fn name(&self) -> String {
        format!(
            "{}_{}",
            self.mnemonic.to_lowercase(),
            self.format.name.to_lowercase()
        )
    }

    /// Flags this instruction as being able to trap, so needs a `TrapCode` at
    /// compile time to track this.
    pub fn has_trap(mut self) -> Self {
        self.has_trap = true;
        self
    }

    /// Indicate this instruction as needing custom processing.
    pub fn custom(mut self, custom: impl Into<Custom>) -> Self {
        self.custom = custom.into();
        self
    }

    /// Sets the alternate version of this instruction, if it exists.
    pub fn alt(mut self, feature: Feature, alternate: impl Into<String>) -> Self {
        self.alternate = Some(Alternate {
            feature,
            name: alternate.into(),
        });
        self
    }
}

impl core::fmt::Display for Inst {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let Inst {
            mnemonic: name,
            format,
            encoding,
            features,
            alternate,
            has_trap,
            custom,
        } = self;
        write!(f, "{name}: {format} => {encoding}")?;
        write!(f, " [{features}]")?;
        if let Some(alternate) = alternate {
            write!(f, " (alternate: {alternate})")?;
        }
        if *has_trap {
            write!(f, " has_trap")?;
        }
        if !custom.is_empty() {
            write!(f, " custom({custom})")?;
        }
        Ok(())
    }
}

/// An alternate version of an instruction.
///
/// Some AVX-specific context: some instructions have the same semantics in
/// their SSE and AVX encodings. In these cases, we use this structure to record
/// the name of the upgraded version of the instruction, allowing us to replace
/// the SSE instruction with its AVX version during lowering. For AVX, using the
/// VEX-encoded instruction is typically better than its legacy SSE version:
/// - VEX can encode three operands
/// - VEX allows unaligned memory access (avoids additional `MOVUPS`)
/// - VEX can compact byte-long prefixes into the VEX prefix
/// - VEX instructions zero the upper bits of XMM registers by default
pub struct Alternate {
    /// Indicate the feature check to use to trigger the replacement.
    pub feature: Feature,
    /// The full name (see [`Inst::name`]) of the instruction used for
    /// replacement.
    pub name: String,
}

impl core::fmt::Display for Alternate {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "{} => {}", self.feature, self.name)
    }
}
